﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Resources;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

using AjaxControlFramework.Reflection;

namespace AjaxControlFramework
{
    
	public abstract class CallbackStrategy : IAjaxControlStrategy
	{
		//------// Properties \\--------------------------------------------\\
		public virtual IAjaxControl Control { get; set; }
		public virtual CallbackSerializer Serializer { get; set; }
		public virtual ScriptStrategy Scripter { get; set; }
        public virtual AuthenticationStrategy Authenticator { get; set; }
        public virtual Control TargetControl { get; set; }


        private Type _targetControlType = null;
        public virtual Type TargetControlType
        {
            get
            {
                if (_targetControlType == null)
                {
                    _targetControlType = TargetControl.GetType();
                }

                return _targetControlType;
            }
            set
            {
                _targetControlType = value;
            }
        }


        private Type _baseTargetControlType = null;
        public virtual Type BaseTargetControlType
        {
            get
            {
                if (_baseTargetControlType == null)
                {
                    if (TargetControlType != null)
                    {
                        _baseTargetControlType = AjaxControlUtils.ResolveBaseControlType(TargetControlType);
                    }
                    else
                    {
                        _baseTargetControlType = TargetControlType;
                    }
                }

                return _baseTargetControlType;
            }
        }


        private Page _parentPage = null;
        public virtual Page ParentPage
        {
            get
            {
                if (_parentPage == null)
                {
                    if (Control == null) { return null; }

                    if ((Control as Control).Page == null)
                    {
                        _parentPage = (HttpContext.Current.CurrentHandler as Page);

                        if (_parentPage == null)
                        {
                            _parentPage = (Page)HttpContext.Current.Items["AjaxControlFramework.AjaxControlHandler.CurrentPage"];
                        }
                    }
                    else
                    {
                        _parentPage = (Control as Control).Page;
                    }
                }

                return _parentPage;
            }
        }


		protected virtual CallbackStrategyResults CallbackResult { get; set; }
		public virtual EventHandler LoadHandler { get; set; }
        protected virtual bool ExceptionOccurred { get; set; }


        public virtual bool? LoadOutOfBand { get; set; }


        public delegate void InitRecursiveMethod(Control control, Control namingContainer);
        public delegate void LoadRecursiveMethod(Control control);
        public delegate void PreRenderRecursiveInternalMethod(Control control);
        public delegate void UnloadRecursiveMethod(Control control, bool dispose);


        private static Type TypeOfControl = typeof(System.Web.UI.Control);


        private static object InitRecursiveLock = new object();
        private static InitRecursiveMethod _initRecursive = null;
        public static InitRecursiveMethod InitRecursive
        {
            get
            {
                if (_initRecursive == null)
                {
                    lock (InitRecursiveLock)
                    {
                        if (_initRecursive == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "InitRecursive".
                            MethodInfo initRecursiveMethodInfo = TypeOfControl.GetMethod("InitRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { TypeOfControl }, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("InitRecursive", typeof(void), new Type[] { TypeOfControl, TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Callvirt, initRecursiveMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _initRecursive = (InitRecursiveMethod)dynamicMethod.CreateDelegate(typeof(InitRecursiveMethod));
                        }
                    }
                }

                return _initRecursive;
            }
        }


        private static object LoadRecursiveLock = new object();
        private static LoadRecursiveMethod _loadRecursive = null;
        public static LoadRecursiveMethod LoadRecursive
        {
            get
            {
                if (_loadRecursive == null)
                {
                    lock (LoadRecursiveLock)
                    {
                        if (_loadRecursive == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "LoadRecursive".
                            MethodInfo loadRecursiveMethodInfo = TypeOfControl.GetMethod("LoadRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, Type.EmptyTypes, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("LoadRecursive", typeof(void), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Callvirt, loadRecursiveMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _loadRecursive = (LoadRecursiveMethod)dynamicMethod.CreateDelegate(typeof(LoadRecursiveMethod));
                        }
                    }
                }

                return _loadRecursive;
            }
        }


        private static object PreRenderRecursiveInternalLock = new object();
        private static PreRenderRecursiveInternalMethod _preRenderRecursiveInternal = null;
        public static PreRenderRecursiveInternalMethod PreRenderRecursiveInternal
        {
            get
            {
                if (_preRenderRecursiveInternal == null)
                {
                    lock (PreRenderRecursiveInternalLock)
                    {
                        if (_preRenderRecursiveInternal == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "PreRenderRecursiveInternal".
                            MethodInfo preRenderRecursiveInternalMethodInfo = TypeOfControl.GetMethod("PreRenderRecursiveInternal", (BindingFlags.NonPublic | BindingFlags.Instance), null, Type.EmptyTypes, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("PreRenderRecursiveInternal", typeof(void), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Callvirt, preRenderRecursiveInternalMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _preRenderRecursiveInternal = (PreRenderRecursiveInternalMethod)dynamicMethod.CreateDelegate(typeof(PreRenderRecursiveInternalMethod));
                        }
                    }
                }

                return _preRenderRecursiveInternal;
            }
        }


        private static object UnloadRecursiveLock = new object();
        private static UnloadRecursiveMethod _unloadRecursive = null;
        public static UnloadRecursiveMethod UnloadRecursive
        {
            get
            {
                if (_unloadRecursive == null)
                {
                    lock (UnloadRecursiveLock)
                    {
                        if (_unloadRecursive == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "UnloadRecursive".
                            MethodInfo unloadRecursiveMethodInfo = TypeOfControl.GetMethod("UnloadRecursive", (BindingFlags.NonPublic | BindingFlags.Instance), null, new Type[] { typeof(bool) }, null);
                            DynamicMethod dynamicMethod = new DynamicMethod("UnloadRecursive", typeof(void), new Type[] { TypeOfControl, typeof(bool) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Callvirt, unloadRecursiveMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _unloadRecursive = (UnloadRecursiveMethod)dynamicMethod.CreateDelegate(typeof(UnloadRecursiveMethod));
                        }
                    }
                }

                return _unloadRecursive;
            }
        }


        public delegate EventHandlerList GetEventsProperty(Control control);


        private static object GetEventsLock = new object();
        private static GetEventsProperty _getEvents = null;
        public static GetEventsProperty GetEvents
        {
            get
            {
                if (_getEvents == null)
                {
                    lock (GetEventsLock)
                    {
                        if (_getEvents == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET protected property "Events".
                            MethodInfo getEventsMethodInfo = TypeOfControl.GetProperty("Events", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(true);
                            DynamicMethod dynamicMethod = new DynamicMethod("GetEvents", typeof(EventHandlerList), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Call, getEventsMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _getEvents = (GetEventsProperty)dynamicMethod.CreateDelegate(typeof(GetEventsProperty));
                        }
                    }
                }

                return _getEvents;
            }
        }


        public delegate void SetParentField(Control control, Control parent);


        private static object SetParentLock = new object();
        private static SetParentField _setParent = null;
        public static SetParentField SetParent
        {
            get
            {
                if (_setParent == null)
                {
                    lock (SetParentLock)
                    {
                        if (_setParent == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET private field "_parent".
                            FieldInfo setParentFieldInfo = TypeOfControl.GetField("_parent", BindingFlags.NonPublic | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("_parent", typeof(void), new Type[] { TypeOfControl, TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Stfld, setParentFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _setParent = (SetParentField)dynamicMethod.CreateDelegate(typeof(SetParentField));
                        }
                    }
                }

                return _setParent;
            }
        }


        public delegate object GetEventKeyField(Control control);


        private static object EventInitLock = new object();
        private static GetEventKeyField _eventInit = null;
        public static GetEventKeyField EventInit
        {
            get
            {
                if (_eventInit == null)
                {
                    lock (EventInitLock)
                    {
                        if (_eventInit == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal static field "EventInit".
                            FieldInfo eventInitFieldInfo = TypeOfControl.GetField("EventInit", BindingFlags.NonPublic | BindingFlags.Static);
                            DynamicMethod dynamicMethod = new DynamicMethod("EventInit", typeof(object), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldfld, eventInitFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _eventInit = (GetEventKeyField)dynamicMethod.CreateDelegate(typeof(GetEventKeyField));
                        }
                    }
                }

                return _eventInit;
            }
        }


        private static object EventLoadLock = new object();
        private static GetEventKeyField _eventLoad = null;
        public static GetEventKeyField EventLoad
        {
            get
            {
                if (_eventLoad == null)
                {
                    lock (EventLoadLock)
                    {
                        if (_eventLoad == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal static field "EventLoad".
                            FieldInfo eventLoadFieldInfo = TypeOfControl.GetField("EventLoad", BindingFlags.NonPublic | BindingFlags.Static);
                            DynamicMethod dynamicMethod = new DynamicMethod("EventLoad", typeof(object), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldfld, eventLoadFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _eventLoad = (GetEventKeyField)dynamicMethod.CreateDelegate(typeof(GetEventKeyField));
                        }
                    }
                }

                return _eventLoad;
            }
        }


        private static object EventUnloadLock = new object();
        private static GetEventKeyField _eventUnload = null;
        public static GetEventKeyField EventUnload
        {
            get
            {
                if (_eventUnload == null)
                {
                    lock (EventUnloadLock)
                    {
                        if (_eventUnload == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal static field "EventUnload".
                            FieldInfo eventUnloadFieldInfo = TypeOfControl.GetField("EventUnload", BindingFlags.NonPublic | BindingFlags.Static);
                            DynamicMethod dynamicMethod = new DynamicMethod("EventUnload", typeof(object), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldfld, eventUnloadFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _eventUnload = (GetEventKeyField)dynamicMethod.CreateDelegate(typeof(GetEventKeyField));
                        }
                    }
                }

                return _eventUnload;
            }
        }


        public virtual Delegate InitDelegate { get; set; }
        public virtual Delegate LoadDelegate { get; set; }
        public virtual Delegate UnloadDelegate { get; set; }


        private bool? _controlInvokedCallback = null;
        public virtual bool ControlInvokedCallback
        {
            get
            {
                if (!_controlInvokedCallback.HasValue)
                {
                    Control control = Control as Control;

                    string uniqueID = control.UniqueID;

                    if (uniqueID == null)
                    {
                        uniqueID = (string)AjaxControlHandler.CallbackArguments.Properties["UniqueID"];
                    }

                    if (uniqueID == null)
                    {
                        return false;
                    }
                    else
                    {
                        if (uniqueID == AjaxControlHandler.ControlID)
                        {
                            _controlInvokedCallback = true;
                        }
                        else
                        {
                            _controlInvokedCallback = false;
                        }
                    }
                }

                return _controlInvokedCallback.Value;
            }
            set
            {
                _controlInvokedCallback = value;
            }
        }


        public static bool OutOfProcessControlIsLoaded
	    {
	        get
	        {
                if (HttpContext.Current.Items["AjaxControlFramework.CallbackStrategy.OutOfProcessControlIsLoaded"] != null)
                {
                    return (bool)HttpContext.Current.Items["AjaxControlFramework.CallbackStrategy.OutOfProcessControlIsLoaded"];
                }

	            return false;
	        }
            set
            {
                HttpContext.Current.Items["AjaxControlFramework.CallbackStrategy.OutOfProcessControlIsLoaded"] = value;
            }
	    }


        public delegate void EnableEventValidationField(Page parentPage, bool enabled);


        private static object EnableEventValidationLock = new object();
        private static EnableEventValidationField _enableEventValidation = null;
        public static EnableEventValidationField EnableEventValidation
        {
            get
            {
                if (_enableEventValidation == null)
                {
                    lock (EnableEventValidationLock)
                    {
                        if (_enableEventValidation == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET private field "_enableEventValidation".
                            FieldInfo enableEventValidationFieldInfo = typeof(Page).GetField("_enableEventValidation", BindingFlags.NonPublic | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("_enableEventValidation", typeof(void), new Type[] { typeof(Page), typeof(bool) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Stfld, enableEventValidationFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _enableEventValidation = (EnableEventValidationField)dynamicMethod.CreateDelegate(typeof(EnableEventValidationField));
                        }
                    }
                }

                return _enableEventValidation;
            }
        }
        //------\\ Properties //--------------------------------------------//



        //------// Constructors \\------------------------------------------\\
		protected CallbackStrategy(Control control)
		{
			if (control == null) { throw new ArgumentNullException("control"); }
			if (!(control is IAjaxControl)) { throw new ArgumentException("A reference of type IAjaxControl was expected.", "control"); }

			Control = (IAjaxControl)control;
		}
		//------\\ Constructors //------------------------------------------//



		//------// Methods \\-----------------------------------------------\\
        // This method is here, rather than in a static method of AjaxControl along with 
        // AjaxControl.Initialize, because it become overrideable so implementors can 
        // have their way with it.
		public virtual void InitializeCallback()
		{
            if (!String.IsNullOrWhiteSpace(AjaxControlHandler.ControlID))
            {
                if (String.IsNullOrWhiteSpace(HttpContext.Current.Request.Form["__EVENTVALIDATION"]))
                {
                    EnableEventValidation(ParentPage, false);
                }
            }

            (Control as Control).Init += new EventHandler(SetupControl);
            (Control as Control).Load += new EventHandler(ControlLoadHandler);
            (Control as Control).PreRender += new EventHandler(ControlPreRenderHandler);
		}


        protected virtual void SetupControl(object sender, EventArgs args)
        {
            if (AjaxControlHandler.IsCallback)
            {
                if (ControlInvokedCallback && String.IsNullOrWhiteSpace(AjaxControlHandler.CallbackArguments.OutOfBandID)) // Don't do this for Out-of-band controls because the AjaxControlHandler will have already Loaded the control.
                {
                    LoadControl(AjaxControlHandler.CallbackArguments);
                }

                LoadControlState();
            }
        }


        protected virtual void ControlLoadHandler(object sender, EventArgs args)
        {
            if (!AjaxControlHandler.IsCallback)
            {
                Scripter.RegisterFrameworkScript();
                Scripter.RegisterControlScript();
            }
        }


        // TODO: Determine if this method is even relevant anymore.
        protected virtual void SetupControlEvents()
        {
            Control control = Control as Control;

            // Detach all InitEvent and LoadEvent handlers from the control. Hold on to these
            // handlers until the callback logic is invoked with a call to the "RaiseCallbackEvent" method.
            EventHandlerList handlerList = GetEvents(control);

            InitDelegate = handlerList[EventInit(control)];
            handlerList.RemoveHandler(EventInit(control), InitDelegate);

            LoadDelegate = handlerList[EventLoad(control)];
            handlerList.RemoveHandler(EventLoad(control), LoadDelegate);


            // *NOTE* Unfortunately, we can only detach Init and Load event handlers. Any OnInit or 
            // OnLoad overloads of the control will still be invoked. 
        }


        protected virtual void LoadControlState()
        {
            if (Control.StateStrategy != null && (Control as Control).EnableViewState && (Control as Control).Parent != null && (Control as Control).Parent.EnableViewState && ParentPage.IsPostBack)
            {
                if ((AjaxControlHandler.IsCallback && ControlInvokedCallback) || !AjaxControlHandler.IsCallback)
                {
                    Control.StateStrategy.Load();
                }
            }
        }


        protected virtual void ControlPreRenderHandler(object sender, EventArgs args)
        {
            if (!Control.IsUpdating && (!AjaxControlHandler.IsCallback || (AjaxControlHandler.IsCallback && ControlInvokedCallback)))
            {
                RenderingHelper.RenderControlWithMarkers(Control as Control, new RenderingHelper.BeforeRendering(delegate()
                {
                    // Depending on what medium the control is configured to have its 
                    // state saved to, there may or may not be the need for the initial 
                    // state footprint to be persisted. In the case of the DiskStateStrategy, 
                    // if the state is not saved on initial load (!ParentPage.IsPostBack) then 
                    // no state will be retrieved upon any callbacks that follow since none 
                    // will exist.

                    // *Note* - We're doing this here so that the JsonStateStrategy and the 
                    // DiscStateStrategy classes can BOTH properly persist the initial control 
                    // state without having to write logic for each specific StateStrategy.
                    if (ParentPage != null && !AjaxControlHandler.IsCallback && Control.StateStrategy != null && (Control as Control).EnableViewState)
                    {
                        Control.StateStrategy.Save();
                    }

                    Scripter.RegisterCreateScript();
                }));
            }
        }


		/// <remarks>This is a default implementation and can be overriden.</remarks>
        public virtual void ProcessCallback(AjaxControlContext controlContext)
		{
            try
            {
                CallbackStrategyArguments jsObject = controlContext.CallbackArguments;


                // 1.) Update the Control's properties with the values sent through the Callback arguments.
                //UpdateControlProperties(jsObject.Properties);


                // 2.) Process all of the callback arguments.
                if (jsObject.CallbackMethod == null)
                {
                    // TODO: Throw a more descriptive exception stating that the "callbackMethod" argument is missing and is required.
                }


                // 3.) Invoke the correct processing method based on the value of the "callbackMethod" argument.
                switch (jsObject.CallbackMethod)
                {
                    case "Invoke":

                        if (jsObject.MethodName == null)
                        {
                            // TODO: Throw a more descriptive exception stating that the "methodName" argument is missing and is required for the "Invoke" callback method.
                        }


                        CallbackResult = Invoke(controlContext);

                        break;

                    case "GlobalInvoke":

                        if (jsObject.ControlType == null)
                        {
                            // TODO: Throw a more descriptive exception stating that the "methodName" argument is missing and is required for the "Invoke" callback method.
                        }

                        if (jsObject.MethodName == null)
                        {
                            // TODO: Throw a more descriptive exception stating that the "methodName" argument is missing and is required for the "Invoke" callback method.
                        }


                        CallbackResult = GlobalInvoke(controlContext);

                        break;

                    case "Update":

                        CallbackResult = Update(controlContext);

                        break;

                    case "Load":

                        if (jsObject.ControlType == null)
                        {
                            // TODO: Throw a more descriptive exception stating that the "methodName" argument is missing and is required for the "Invoke" callback method.
                        }


                        CallbackResult = Load(controlContext);

                        break;

                    default:

                        throw new System.Exception("There is no server method signature that matches the method the client-side has invoked.");

                }
            }
            catch (System.ApplicationException applicationException)
            {
                if (applicationException.Message == new ResourceManager("System.Web", typeof(System.Web.HttpApplication).Assembly).GetString("Redirect_not_allowed_in_callback"))
                {

                }
                else
                {
                    ExceptionOccurred = true;

                    CallbackResult = new CallbackStrategyResults();
                    CallbackResult["Exception"] = applicationException;
                }
            }
            catch (System.Exception exception)
            {
                ExceptionOccurred = true;

                CallbackResult = new CallbackStrategyResults();
                CallbackResult["Exception"] = exception;
            }
		}


		/// <remarks>This is a default implementation and can be overriden.</remarks>
		public virtual string GenerateCallbackResult(AjaxControlContext controlContext)
		{
            GenerateResultsFromScriptManager(controlContext);


            if (ExceptionOccurred && CallbackResult["Exception"] != null && CallbackResult["Exception"] is System.Exception)
            {
                return "e" + Serializer.SerializeException(((System.Exception)CallbackResult["Exception"]).GetBaseException());
            }
            else
            {
                // Handle the RedirectOptions.
                if (ParentPage != null && !String.IsNullOrEmpty(ParentPage.Response.RedirectLocation))
                {
                    CallbackResult["RedirectOptions"] = new RedirectOptions(HttpContext.Current.Response.RedirectLocation);
                }
                else if (Control.AjaxContext.RedirectOptions != null && !String.IsNullOrEmpty(Control.AjaxContext.RedirectOptions.RedirectUrl))
                {
                    CallbackResult["RedirectOptions"] = Control.AjaxContext.RedirectOptions;
                }

                // Update the values of the Properites.
                CallbackResult["Properties"] = GenerateResultPropertyValues();

                // Pass back the State collection;
                CallbackResult["State"] = Control.AjaxContext.State;

                return "s" + Serializer.SerializeCallbackResult(CallbackResult);
            }
		}


        // This method helps a bit with increasing the AJAX Control Framework's compatibility with web applications that make use of 
        // some of ASP.NET's older scripting components and those that make use of standard ASP.NET Validators within AJAX Controls or 
        // UpdateRegions.
        protected virtual void GenerateResultsFromScriptManager(AjaxControlContext controlContext)
        {
            // Declare the Validators that need to be 
            

            // TODO: Move all of the script generating parts of this method to the ScriptStrategy.
            

            System.Web.UI.ScriptManager scriptManager = System.Web.UI.ScriptManager.GetCurrent(ParentPage);
            if (scriptManager == null) { return; }

            var validatorDeclarations = scriptManager.GetRegisteredArrayDeclarations().Where(s => s.Name == "Page_Validators");
            var expandoAttributes = scriptManager.GetRegisteredExpandoAttributes();

            Dictionary<string, ValidatorDeclaration> validators = new Dictionary<string, ValidatorDeclaration>();
            Dictionary<string, object> otherExpandos = new Dictionary<string,object>();

            foreach (var validatorDeclaration in validatorDeclarations)
            {
                validators.Add(validatorDeclaration.Control.ClientID, new ValidatorDeclaration(validatorDeclaration.Value, new Dictionary<string, object>()));
            }

            foreach (var expando in expandoAttributes)
            {
                if (validators.ContainsKey(expando.ControlId))
                {
                    validators[expando.ControlId].Expandos.Add(expando.Name, expando.Value);
                }
                else
                {
                    otherExpandos.Add(expando.ControlId, new { Name = expando.Name, Value = expando.Value });
                }
            }


            var startupScripts = scriptManager.GetRegisteredStartupScripts();
            List<string> otherScripts = new List<string>();
            
            foreach (var startupScript in startupScripts)
            {
                if (startupScript.Key != "ValidatorIncludeScript")
                {
                    otherScripts.Add(startupScript.Script);
                }
            }
            
            
            if (controlContext.ValidationScriptShouldBeLoaded)
            {
                otherScripts.Add(Scripter.GetValidatorScript());
            }


            if (validators.Count > 0)
            {
                CallbackResult["Validators"] = validators;
            }

            if (otherExpandos.Count > 0)
            {
                CallbackResult["Expandos"] = otherExpandos;
            }

            if (otherScripts.Count > 0)
            {
                CallbackResult["StartupScripts"] = otherScripts;
            }
        }


        public virtual void UpdateControlProperties(Dictionary<string, object> propertyValues)
        {
            // This method is only meant to be called during Callbacks AND (against TargetControls that the callback was 
            // actually invoked against OR against a TargetControl that was not the source of the callback but was created 
            // as part of a "Load" call). This pretty much means that the "UpdateControlProperties" method will be called 
            // for the following conditions:
            // 1.) "Invoke" and "Update" calls IF the TargetControl is the source of the callback.
            // 2.) "Load" calls if the TargetControl is the control being loaded.
            if (!ControlInvokedCallback && AjaxControlHandler.CallbackArguments.CallbackMethod != "Load") { return; }

            Dictionary<string, PropertySetCacheEntry> propertySetters = PropertyCache.GetPropertySettersOfType(TargetControlType);

            foreach (string propertyName in propertyValues.Keys)
            {
                if (propertySetters.ContainsKey(propertyName))
                {
                    PropertySetCacheEntry setEntry = propertySetters[propertyName];

                    if (setEntry.Delegate != null && (!setEntry.Attribute.UpdateOnCallback.HasValue || setEntry.Attribute.UpdateOnCallback.Value == true))
                    {
                        setEntry.Delegate(TargetControl, propertyValues[propertyName]);
                    }
                }
            }
        }


        protected virtual Dictionary<string, object> GenerateResultPropertyValues()
        {
            Dictionary<string, PropertyGetCacheEntry> propertyGetters = PropertyCache.GetPropertyGettersOfType(TargetControlType);
            Dictionary<string, object> propertyValues = new Dictionary<string, object>();

            foreach (PropertyGetCacheEntry getter in propertyGetters.Values)
            {
                if (getter.Delegate != null)
                {
                    propertyValues.Add(getter.ItemName, getter.Delegate(TargetControl));
                }
            }

            return propertyValues;
        }
		//------\\ Methods //-----------------------------------------------//



		//------// Abstract Methods \\--------------------------------------\\
        public abstract void LoadControl(CallbackStrategyArguments callbackArguments);
        public abstract AjaxControlContext GenerateControlContext(CallbackStrategyArguments callbackArguments);

		protected abstract CallbackStrategyResults Load(AjaxControlContext controlContext);
        protected abstract CallbackStrategyResults Update(AjaxControlContext controlContext);

        protected abstract CallbackStrategyResults GlobalInvoke(AjaxControlContext controlContext);
        protected abstract CallbackStrategyResults Invoke(AjaxControlContext controlContext);
		//------\\ Abstract Methods //--------------------------------------//
	}
}
